package awk.proxy;

import awk.model.User;
import awk.model.animal.Animal;
import awk.model.animal.Dog;
import awk.model.animal.MuYangDog;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.*;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.junit.Test;

/**
 * method.invoke() 对多态的支持
 * invoke()方法中主要分为两部分：访问控制检查和调用MethodAccessor.invoke()实现方法执行。
 * https://blog.csdn.net/wenyuan65/article/details/81145900
 *
 * MethodHandle反射调用也是支持多态的，并且和Method不同的是，MethodHandle的成员方法要先bindTo到某个instance，bind过程中已经做了类型检查
 * MethodHandle更像是在Java语法规则内手写字节码：自己创建方法签名（MethodType），自己决定调用方式（invokestatic/invokespecial/invokevirtual），
 * 自己注意访问控制（public/package/protected/private），最后还要自己决定类型隐式转换；而Method权限则大得多。虽然MethodHandle能力相对受限，不过性能确高了很多
 *
 * jdk中的动态代理
 * 通过反射类Proxy和InvocationHandler回调接口实现，要求委托类必须实现一个接口，
 * 只能对该类接口中定义的方法实现代理，这在实际编程中有一定的局限性
 * 使用cglibd代理
 * 并不要求委托类必须实现接口，底层采用asm字节码生成框架生成代理类的字节码，不能处理被final关键字修饰的方法
 *
 * 动静态代理的区别，什么场景使用？（2015-11-25）
 * 静态代理通常只代理一个类，动态代理是代理一个接口下的多个实现类。
 * 静态代理事先知道要代理的是什么，
 * 而动态代理不知道要代理什么东西，只有在运行时才知道。 动态代理是实现 JDK 里的 InvocationHandler 接口的invoke 方法，但注意的是代理的是接口，也就是你的业务类必须要实现接口，通过 Proxy 里的 newProxyInstance 得到代理对象。
 * 还有一种动态代理 CGLIB，代理的是类，不需要业务类继承接口，通过派生的子类来实现代理。通过在运行时，动态修改字节码达到修改类的目的。
 * AOP编程就是基于动态代理实现的，比如著名的 Spring 框架、Hibernate 框架等等都是动态代理的使用例子。
 */
public class MainRun {


	public static void main(String[] args) throws IllegalAccessException, InstantiationException {
		//静态代理
		System.out.println("静态代理————————————————————");
		//静态代理：客户端实例化代理，通过代理取 子类的引用
		StaticProxy staticProxy = new StaticProxy();
		staticProxy.setDog(new Dog());
		staticProxy.sleep();

		//Cglib代理
		System.out.println("cglib动态代理————————————————————");
		testCglibProxy2();

		//jdk代理
		System.out.println("jdk动态代理————————————————————");
		testJdkDynamicProxy2();

	}

	public static void testCglibProxy2() {
		Enhancer enhancer = new Enhancer();
		//设置公共的接口或者公共的类
		enhancer.setSuperclass(MuYangDog.class);
		//建立关联关系
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
				return methodProxy.invokeSuper(o, objects);
			}
		});
		Dog proxyDog = (Dog) enhancer.create();
		proxyDog.eat();
	}

	public static void testJdkDynamicProxy2() {
		Dog dog = new Dog();
		InvocationHandler invocationHandler = new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				return method.invoke(dog, args);
			}
		};
		Animal proxyDog = (Animal) Proxy.newProxyInstance(dog.getClass().getClassLoader(), dog.getClass().getInterfaces(), invocationHandler);
		proxyDog.eat();
	}


	@Test
	public void testMethodHandle() throws Throwable {
		// public方法的Lookup
		MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
		// 所有方法的Lookup
		//MethodHandles.Lookup lookup = MethodHandles.lookup();
		MethodType mt = MethodType.methodType(void.class, String.class);
		MethodHandle methodHandle = publicLookup.findVirtual(Dog.class, "eat", mt);

		//bindTo 作用
		//methodHandle.bindTo(xxx).invoke(new MemberService(),new String[]{"awk", "anna"});

		Object invoke = methodHandle.invoke(new Dog(),"骨头");
		// methodHandle.invoke(),
		// methodHandle.invokeWithArugments()
		// methodHandle.invokeExact()
		System.out.println(invoke);
	}

	@Test
	public void testMethod() throws Exception {
		//分别用Dog和MuYangDog的实例对象去执行sleep方法
		Dog dog = new Dog();
		MuYangDog muYangDog = new MuYangDog();

		//MuYangDog 类覆盖了父类Dog的sleep方法，通过反射分别获取sleep的Method对象
		Method dogMethod = Dog.class.getDeclaredMethod("sleep");
		dogMethod.invoke(dog); //对象的真实类型和Method的声明Classs是相同的，按照预期打印结果
		dogMethod.invoke(muYangDog);  //MuYangDog是Dog的子类，按照多态的特性，子类调用父类的的方法，方法执行时会动态链接到子类的实现方法上

		Method muyangMethod = MuYangDog.class.getDeclaredMethod("eat");
		muyangMethod.invoke(muYangDog);  //对象的真实类型和Method的声明Classs是相同的，按照预期打印结果
		muyangMethod.invoke(dog); //父类调用子类的方法，因此这一次会抛出异常
	}


	public static void testInvoke() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
		//获得Class对象
		Class c1 = Class.forName("awk.model.User");
		//构造一个对象(强制转换成User)
		//Object o = c1.newInstance();
		//User user = (User)c1.newInstance(); //本质上是调用了类的无参构造器
		//System.out.println(user);

		//通过构造器创建对象，User类要重写toString。
		//Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
		//User user2 = (User)constructor.newInstance("qingjiang", 001, 18);
		//System.out.println(user2);
		//如果你发现你输出的对象是一串字符串，说明你可能没在实体中加入toString方法。

		//通过反射调用普通方法
		User user3 = (User)c1.newInstance();
		//通过反射获取一个方法
		Method setName = c1.getDeclaredMethod("setName", String.class);
		//invoke:激活的意思
		//传入（对象， “方法的值”）
		setName.invoke(user3, "kuangshen");
		System.out.println(user3.getName());

		//通过反射操作属性，不能直接操作私有属性，需要关闭程序的安全检测，把属性或者方法的setAccessible设为true即可。
		System.out.println("*****************");
		User user4 = (User)c1.newInstance();
		Field name = c1.getDeclaredField("name");

		//关掉私有属性的保护权限
		name.setAccessible(true); //为true可访问
		name.set(user4, "狂神");
		System.out.println(user4.getName());
	}


}
